﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Red.CommUtils;
using Red.CommUtils.Application;
using Red.Controls.Utils;
using Red.Controls.ViewModels;
using Red.Core;
using Red.EngineData.Protocol.Interop;
using Red.EngineData.View;
using Red.GameData;

namespace Red.Controls.Shell
{
    /// <summary>
    ///     Shared data systems for all editors.
    ///     HERE go all the shared data systems exposed as properties - for the GameData, EngineData, etc.
    /// </summary>
    public class EditorSharedData
    {
        #region Data

        private InteropDataItem _engineObjectRoot;
        private bool _engineDataInitialized;
        private readonly AsyncLock _engineDataLock = new AsyncLock();
        private readonly IAppLauncher _engineLauncher;
        private IAppHandle _engineApp;
        private AppMonitor _engineAppMonitor = new AppMonitor();
        private EditorEnvironment _gameData;
        private readonly AsyncLock _gameDataLock = new AsyncLock();
        private bool _gameDataInitialized;
        private readonly string _gameDataPath;
        private readonly string _gameRootPath;

        #endregion Data

        #region Properties

        /// <summary>
        ///     Gives access to the depot
        /// </summary>
        public DepotInfo DepotInfo { get; private set; }

        /// <summary>
        ///     Gives access to the engine data. You should await the result (to cope with possibly not-finished initialization)
        /// </summary>
        public Task<InteropDataItem> EngineObjectRoot { get { return GetEngineObjectRootAsync(); } }

        /// <summary>
        ///     Gives access to the game data. You should await the result (to cope with possibly not-finished initialization)
        /// </summary>
        public Task<EditorEnvironment> GameData { get { return GetGameDataAsync(); } }

        /// <summary>
        ///     Gives access to the engine app.
        /// </summary>
        public IAppHandle EngineApp
        {
            get { return _engineApp; }
        }

        /// <summary>
        ///     Gives access to engine app monitor.
        /// </summary>
        public AppMonitor EngineMonitor
        {
            get { return _engineAppMonitor; }
        }

        /// <summary>
        ///     Gives access to resource creation/importing handler class
        /// </summary>
        public ResourceFactory ResourceFactory { get; private set; }

        public string EnginePath { get; private set; }

        #endregion Properties

        #region C-tor

        public EditorSharedData( DepotInfo depotInfo, IAppLauncher engineLauncher, string gameDataPath, string enginePath, string gameRootPath )
        {
            DepotInfo = depotInfo;
            ResourceFactory = new ResourceFactory( this );
            _engineLauncher = engineLauncher;
            _gameDataPath = gameDataPath;
            _gameRootPath = gameRootPath;
            EnginePath = enginePath;
        }

        #endregion C-tor

        #region Public methods

        /// <summary>
        ///     Starts the initialization of the EngineData. The process may take some time to load and initialize the engine.
        /// </summary>
        /// <exception cref="CommunicationException">
        ///     Initialization of communication channel with engine failed
        /// </exception>
        public async Task InitEngineDataAsync()
        {
            using ( await _engineDataLock.LockAsync() )
            {
                // GENERAL NOTE: when using AsyncLock we HAVE to use awaitable version of async functions (the callback based won't be locked!)
                if ( !_engineDataInitialized )
                {
                    try
                    {
                        _engineApp = await _engineLauncher.Launch( new[] { "-noviewport", "-engine=CBackendEngine", string.Format( "-gameRoot={0}", _gameRootPath ) } );
                        _engineApp.RequestToolkitService();
                        
                        IChannel mainEngineChannel = _engineApp.Channels.Main;
                        IChannel editorChannel = _engineApp.Channels.Editor;
                        IChannel debugEngineChannel = _engineApp.Channels.Debug;
                        IViewUpdateScheduler viewUpdateScheduler = new PullActionResponseScheduler();
                        InteropProtocol protocol = new InteropProtocol( mainEngineChannel, editorChannel, viewUpdateScheduler );

                        var factory = new DataViewFactory( protocol );
                        var rootView = factory.CreateRoot();
                        _engineObjectRoot = await InteropDataItem.CreateRoot( rootView );
                        if ( _engineObjectRoot.IsValid )
                        {
                            _engineDataInitialized = true;
                        }
                        else
                        {
                            await RedMessageBox.ShowAsync( null, RedDialogType.Ok, "Engine data root initialization exception:\n" + _engineObjectRoot.LastErrorMessage );
                        }

                        // NOTE: we do not include debugChannel in app monitor, as it's for getting some statistics.
                        _engineAppMonitor.AttachToChannel( debugEngineChannel, new List<IChannel>() { mainEngineChannel, editorChannel } );
                        _engineAppMonitor.AttachToInteropDebugger( protocol );
                        _engineAppMonitor.AttachToAppHandle( _engineApp );
                        _engineAppMonitor.Enabled = true;
                    }
                    catch ( IOException ex )
                    {
                        OnInitializationError( ex );
                    }
                }
            }
        }

        public async Task InitGameDataAsync()
        {
            using ( await _gameDataLock.LockAsync() )
            {
                if ( !_gameDataInitialized )
                {
                    try
                    {
                        _gameData = await Task.Run( () =>
                        {
                            var dataPath = _gameDataPath;
                            var reflectionPath = Path.Combine( EnginePath, @"schemas\" );
                            if ( !Path.IsPathRooted( _gameDataPath ) )
                            { // if relative path is given, assume it's rooted in a depot root
                                dataPath = Path.Combine( DepotInfo.RootPath, dataPath );
                                reflectionPath = Path.Combine( DepotInfo.RootPath, reflectionPath );
                            }
                            var log = new GameDataLogReporter();
                            var gameData = EditorEnvironment.Setup( dataPath, reflectionPath, log );
                            if ( gameData == null )
                                throw new IOException( log.ErrorMessage );

                            _gameDataInitialized = true;
                            return gameData;
                        } );
                    }
                    catch ( IOException ex )
                    {
                        OnInitializationError( ex );
                    }
                }
            }
        }

        #endregion Public methods

        #region Events

        public delegate void InitializationErrorHandler( Exception ex );

        public event InitializationErrorHandler InitializationError;
        private void OnInitializationError( Exception ex )
        {
            InitializationError?.Invoke( ex );
        }

        #endregion Events

        #region Private methods

        private async Task<InteropDataItem> GetEngineObjectRootAsync()
        {
            await InitEngineDataAsync();
            return _engineObjectRoot;
        }

        private async Task<EditorEnvironment> GetGameDataAsync()
        {
            await InitGameDataAsync();
            return _gameData;
        }

        #endregion Private methods
    }
}